7. 地址转换

虚拟化 CPU 时类似,我们同样需要保证内存的虚拟化是高效、可控、灵活的。高效意味着我们需要硬件的支持;可控意味着操作系统必须确保应用程序只能访问自己的地址空间;此外灵活性要求程序能以任何方式访问自己的地址空间,这也方便了程序与系统的编写。因此,我们将同样采取 受限直接访问 的思路构建这一机制,即操作系统应尽量让程序自己运行,保持在关键点能够直接介入来保持对硬件的控制。

关键问题:如何高效、可控、灵活地虚拟化内存?

操作系统用以支持地址空间这一内存虚拟化的底层机制便是 基于硬件的地址转换。利用地址转换,硬件对每次内存访问进行处理时都会将指令中的虚拟地址转换为数据实际存储的物理地址。同样这一过程需要操作系统的介入:需要设置好硬件以完成正确的地址转换;需要记录内存的占用情况;需要在特定时机下介入。操作系统与硬件协作以实现 地址空间 的假象,来支持多个程序共享物理内存。

为了方便叙述,我们采用与 4. 进程调度策略 类似的方式:先提出一些不切实际的假设,讨论理想情况下的机制,随后逐步去掉这些假设,讨论现实情况中的地址转换与内存分配的机制。

我们对进程的内存空间作出如下假设:

  1. 地址空间必须在物理内存中连续。
  2. 地址空间不是很大(小于物理内存的大小)。
  3. 所有进程的地址空间大小相同。

基本想法:基于硬件的动态重定位

在这些假设下,解决方法似乎十分简单。最常见的一个想法即是 动态重定位(Dynamic Relocation)机制,又称 基址加界限(Base and Bound)机制。其思路很简单:每个 CPU 维护一对硬件寄存器:基址寄存器与界限寄存器。我们有时称 CPU 中负责地址转换的部分称作 内存管理单元(Memory Management Unit,MMU)。当程序运行时,操作系统决定该程序的地址空间在物理内存中的实际的起始地址,并将其记录在基址寄存器中;界限寄存器负责提供访问保护,操作系统决定程序的地址空间大小,并将其记录在界限寄存器中。由于进程的地址空间从零开始,则进程产生的所有 虚拟 内存引用都可以在加上基址寄存器的值后得到实际的物理地址。而当进程试图访问负的或超出地址空间的虚拟地址时,CPU 会触发异常并将控制权交给操作系统中负责越界的异常处理程序处理,操作系统可能会终止该进程。这便是地址转换技术。由于这种重定位在程序运行时发生,因此也被称作动态重定位。

虚拟化 CPU 时类似,为了实现上面所述的机制,需要硬件与操作系统共同支持更多操作。硬件需要提供:

Pasted image 20250609181009.png

而操作系统需要实现如下的功能:

Pasted image 20250609181902.png

与前面 CPU 的受限直接执行 结合起来,我们得到了一个在大多数情况下应用程序与操作系统、硬件的交互逻辑的表格:

Pasted image 20250609181940.png
Pasted image 20250609181956.png

可以看到,在这个过程中,在操作系统设置好硬件后,进程就可以自行在 CPU 上运行,虚拟地址的转换过程也完全由硬件处理。操作系统只会在时机合适(如中断发生时)才会介入,这完美符合我们一开始的设计思路

然而,到现在我们所建立的只是一个基于理想环境的最基本的内存访问机制。即使不去掉我们在上面提出的几条假设。这个机制仍然存在内存利用率不高的问题。具体而言:实际运行的进程并不会有特别大的栈区或堆区,这导致堆栈中间大量的空闲内存被浪费了。这被称作 内部碎片(Internal Fragmentation),指的是已经分配的内存单元内部存在碎片(即未使用的内存空间)从而造成了浪费。下一节将提出该问题的一个解决方法,在 MMU 中为地址空间中的每个逻辑段分配一对基址与界限寄存器,即 分段。我们也将看到,分段也有自己的问题。